#include "gravity.h"

Gravity::Gravity(float gravityAccel){
	this->gravityAccel = gravityAccel;
}

Gravity::~Gravity(){
}

void Gravity::clear(){
	objects.clear();
}

void Gravity::addParticle(Particle* particle){
	this->particles.push_back(particle);
	particle->accelerate(gravityAccel);
}

void Gravity::removeParticle(Particle* particle){
	this->particles.remove(particle);
}

void Gravity::addSolid(Solid* solid){
	this->objects.push_back(PtrSolid(solid));
	solid->accelerate(gravityAccel);
	this->objects.sort();
}

void Gravity::removeSolid(Solid* solid){
	this->objects.remove(PtrSolid(solid));
}

void Gravity::checkSolidCollisions(){
	std::list<PtrSolid> collided;
	for(std::list<PtrSolid>::iterator it=objects.begin();it!=objects.end();++it){
		std::list<PtrSolid>::iterator it2=objects.begin();

		while( it2!=objects.end() ){
			bool collide=false;
			if(it!=it2){
				Cube motion( (*it)->getSize() );
				motion.extend(0.0f,0.0f,(*it)->getDropSpeed());
				collide = motion.intersect((*it2)->getSize());

				if(collide)
					collided.push_back(*it2);
			}

			++it2;
		}


		if(!collided.empty()){
			PtrSolid solid = *collided.back();
			if(fabs((*it)->getDropSpeed())>0){
				(*it)->accelerate(-(*it)->getDropSpeed());
				if(solid->getSize().maxZ()<(*it)->getSize().minZ()){
					(*it)->adjust(solid->getSize().maxZ()-(*it)->getSize().minZ());
				}
				solid->notifyCollision(**it);
				(*it)->notifyCollided(*solid);
			}
			collided.clear();
		}else{
			(*it)->advanceTime();
			(*it)->accelerate(gravityAccel);
		}


		++it2;
	}
}

// Las particulas solo colisionan con objetos solidos.
void Gravity::checkParticleCollisions(){
	std::list<PtrSolid> collided;
	for(std::list<Particle*>::iterator it=particles.begin();it!=particles.end();++it){
		std::list<PtrSolid>::iterator it2=objects.begin();

		bool collide=false;
		while( it2!=objects.end() && !collide){
			Cube motion( (*it)->getSize() );
			motion.extend(0.0f,0.0f,(*it)->getDropSpeed());
			collide = motion.intersect((*it2)->getSize());
//			collide = ((*it)->getSize().minZ()<0);

			// Solo basta saber cuando la particula colisiono.
			// Si no colisiono con el primero no vale la pena seguir buscando,
			// la velocidad es constante
			if(collide)
				(*it)->notifyCollided(**it2);
			else
				break;

		}

		if(!collide){
			(*it)->advanceTime();
			(*it)->accelerate(gravityAccel);
		}
	}
}

void Gravity::advanceTime(){
	checkSolidCollisions();
	checkParticleCollisions();
}
